使你的 Metal 应用程序更好地运行在 Apple Silicon 架构上
作者:提拉拉拉就是技术宅,LinkedIn 高级研发工程师。曾就职于百度。博客:http://yuusann.com
Sessions: https://developer.apple.com/videos/play/wwdc2020/10631/
概述
本文主要讨论今年苹果推出 Apple Silicon 处理器之后会对 Metal 应用会存在哪些影响以及将现存的 Metal 应用程序迁移到 Apple Silicon 上的最佳实践。本文假设读者已经拥有一定的 Metal 开发经验(iOS 或者 macOS 平台),并且对常见的渲染优化技术熟悉。
架构迁移
Apple 推出新的 Apple Silicon 架构以后,Metal 应用程序在新架构下运行和在旧的 Intel 平台下运行的区别:
具体表现为:
在旧的 Intel 平台运行时,代码将直接原生运行。
在 Apple Silicon 平台上运行时,代码会运行在一个高度优化的中间层 —— Rosetta 之上。需要注意的是虽然 Rosetta 经过了优化,但转换仍然存在性能损失。
使用新的 macOS SDK 重新构建和发布能够解决 Rosetta 性能损失的问题。
针对 Apple Silicon 对代码进行优化。关于具体的优化内容,请查看相关专题:Optimize Metal Performance for Apple Silicon Macs(编写进行中)
因此,在新的 SDK 发布以后,重新对应用程序进行构建和发布会,并根据优化指导进行一定代码优化是 Apple 建议的操作。
GPU 层面
在 GPU 层面,Apple Silicon 架构使 macOS 上的 Metal 在得到优化的同时并获得了更多的功能支持。
下面就 GPU 架构变动和 Metal API 变动此两点分别来讲讲。
GPU 架构
此处所描述的 GPU 架构更像是渲染管线的渲染方式,常见的有:
IMR
(Immediate Mode Rendering):每提交一个像素就开始走渲染管线进行渲染,包括被遮挡的部分。在不考虑性能的情况下它的逻辑是最简单的,但 GPU 和会做一些额外的渲染运算且会浪费带宽。TBR
(Tile Based Rendering):将渲染画面分成 32×32 或其他大小的块,然后先对每个 Tile 进行处理,生成一个列表,最后进行渲染。需要注意的是虽然 TBR 的名字里没有 Deferred,但它的逻辑是先对每个 Tail 分块进行处理,最后才开始图形渲染,所以仍然属于延迟渲染技术
的范畴。TBDR
(Tile Based Deferred Rendering):在 TBR 的基础上,加入了HSR
(隐藏面消除技术),剔除遮挡等像素,使 GPU 只渲染屏幕上看得见的像素,从而不做额外的渲染工作。
以上几种都是常见的渲染优化技术,笔者去年在《WWDC19 内参》中的 Session 文章就包含了部分优化技术的细节以及使用 Metal API 的实现方法,有兴趣的读者可以直接查看这篇文章:《基于 Metal 的现代渲染技术》 —— 小专栏《WWDC19 内参》
Tips:这些优化技术并不是百分之百能提升性能的,在某些极端情况下 TBR 在会退化成 IMR,且分块还带来了额外的内存占用。但在大多数情况下,这些解决方案仍然是优于 IMR 的。
在 Apple Silicon 中,Metal 不再使用 IMR,而是转而使用 TBDR,以下是这两种架构的区别:
IMR:
TBDR:
从架构上可以看出两者的区别:
TBDR 将顶点数据首先分块,存在 Tiled Vertex Buffer 中。
在片元着色器渲染之前,通过 HSR 技术移除了不可见的表面从而减少片元着色器的压力。
如果不需要深度缓冲区,可以使用 memoryless 选项不储存这些数据从而节省内存。
alpha 测试、blending 可以在片元中编程处理,因此可以很方便的做可编程混合、MSAA 抗锯齿等。
使用 TBDR 的优点:
减少大量内存和内存带宽使用。
blending 等操作可以在寄存器中完成,性能会有质的飞跃。
在渲染阶段不再需要读取颜色、 深度等缓冲器,进一步减少内存带宽占用。
得益于 GPU 架构的改变,以下所有的框架都可以从中受益。
OpenGL & OpenCL
在讨论 Metal API 之前,先来看一下已经被标记弃用的 OpenGL 和 OpenCL。这两者已经在上个 macOS 版本被标记弃用,但因为还有很多应用程序仍然在使用,所以还未被移除。但这两者的版本已经不再更新了。
目前 macOS 支持的最高版本:
OpenGL: 4.1
OpenCL: 1.1
还在使用这两个框架的应用程序,推荐尽快迁移到 Metal。
Metal API
Apple Silicon 给 macOS 上的 Metal 带来了丰富功能。
图里的技术大部分都是优化技术,部分一些是 iOS 已经支持但在 macOS 上并不支持的。得益于架构的改变,现在 macOS 上也可以使用这些功能了。其中还可以看到刚才已经提过的:Memoryless Render Targets
、Programmable Blending
、On-Chip MSAA Resolve
等。有兴趣的读者可以自行学习。
使用这些技术可以轻松实现任意正向延迟渲染、抗锯齿、各类性能优化。旧的 API 仍然可以使用,但使用新的 API 能使应用最大程度受益。
一些最佳实践
以下是一些在 Apple Silicon 平台中使用 Metal 需要额外注意的问题以及最佳实践,下面就分别来看看吧。
Metal 功能检测
不同的平台对于 Metal 的支持并不相同,在使用之前可能需要检查机器是否支持需要使用的功能。那么检测功能的最佳实践是怎样的呢?
错误的示范:
开发者首先使用宏定义区分 iOS 和 macOS 平台,认为Apple GPU Feature
在 macOS 上不支持。在 Apple Silicon 发布之前,这是正确的,但 Apple Silicon 发布之后,macOS 也支持Apple GPU Feature
了。因此粗暴地用宏定义区分平台是不正确的。
正确的示范:
使用这些 API 能够适应各类 GPU 而不用关心是何平台以及是何品牌。
加载和存储选项
渲染时,管线的loadAction
和storeAction
选项受开发者手动控制,错误地使用选项可能造成性能问题或渲染错误。
下面这个例子中,由于错误的访问选项,出现了渲染错误:
天空盒和场景使用了两个管线进行渲染。先渲染天空盒,然后渲染场景。有经验的 Metal 开发者一定已经看出来是哪儿的问题了。这里错误地将渲染场景时的管线的loadAction
设置为了dontCare
,那么在渲染场景时就会发生渲染错误,正确的做法应该讲loadAction
设置为load
。
在loadAction
设为dontCare
时,系统分配的 Tail 内存将是一块未初始化的内存,因此画面里会出现什么内容是不可预料的。
如果storeAction
是dontCare
,那么渲染结束之后的 Tail 内存不会被写入内存而直接被丢弃。
dontCare
这个选项在正确使用的时候可以减少不必要的内存初始化或是渲染结果保存,可以对性能起到正面作用,但如果使用错误会得到不可预料的渲染结果。这些仍然需要开发者来管理,Apple Silicon 并不能帮助开发者来解决这个问题。
坐标一致性
在有多个渲染管线的场景下,也会出现坐标不一致导致的渲染错误,如下图所示:
顶点着色器代码如下:
其核心问题是两个管线使用各自的顶点着色器,两个着色器函数都调用了computePosition
函数来计算顶点,但由于 Shader 高度优化且这里计算的值也是浮点值,不能保证这两个管线的顶点着色器计算的结果是一致的。
而在渲染时,恰好又把深度测试的方法设置成了EQUAL
,这就导致在剔除遮挡时出现问题,有不该被剔除的面也被剔除了。
解决方法也很简单,只需配置以下高亮区域选项即可:
线程组内存同步
线程组(threadgroup)常用于 Compute Shading。一个线程组可能包含多个 SIMD 组。
如上图,一个 8×8 的线程组就包含了两个 SIMD 组。SIMD 组的大小和 GPU 相关,值一般为 32。组内的线程共用一块内存,如果不对线程加以约束,就有可能引发渲染错误。
代码层面,在对共用内存进行操作的时候,似乎少了点什么?
没错,加上 barrier 防止竞争。
threads_per_simdgroup
可以获取当前 SIMD 组的大小,用来判断使用哪种 barrier。simdgroup_barrier
用于分隔同一个 SIMD 组的线程,threadgroup_barrier
用于分隔同一个线程组的线程。
请尽量减少 barrier 使用,因为它们会影响性能。同样的,纹理和内存 barrier 的开销同样昂贵,因此非必要时请勿使用。
对附件的采样
在同一个渲染管线中,在渲染时不允许对当前管线的深度或模板贴图等附件进行采样,否则会造成并行读写问题引发不可预料的渲染错误。
上图就是在渲染时对深度贴图进行采样造成的。
引发问题的关是下图红色区域位置:
深度贴图会在片元着色器之前生成,此时 GPU 会 flush 贴图内存到系统内存,如果在片元着色器中对深度贴图进行采样,就会引发并行读写问题。
如果应用程序中确实有对这类附件的采样需求,请考虑创建一份拷贝专供采样。
渲染一致性
在 Apple Silicon 的 Mac 推出以后,如何保证以 macOS Catalina 或更早版本的 SDK 所构建的应用程序能够在新的架构上正常运行呢?
Metal 会把所有
loadAction
是DontCare
的覆盖成了load
以防止渲染错误。Metal 会强制打开坐标一致性以防止渲染错误。
如果对本管线的深度贴图进行了采样,Metal 会对其进行快照以防止渲染错误。
以上操作仅针对使用 macOS Catalina 及更老的 SDK 版本构建的应用程序。这些操作意味着性能损失,所以请尽快使用新的 SDK 进行重新发布。
总结
以上就是新 Apple Silicon 平台上关于 Metal 的最佳实践。
Apple Silicon 能够使 Metal 应用程序跑的更快,性能更好,而且这些功能在 Apple 全平台通用。这意味着可以更方便地在各个平台间共享代码。
进一步的优化请查看这个 Session:Optimize Metal Performance for Apple Silicon Macs(编写进行中)
推荐阅读
关注我们
我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。
关注有礼,关注【老司机技术周报】,回复「2020」,领取学习大礼包。
支持作者
这篇文章的内容来自于 《WWDC20 内参》。在这里给大家推荐一下这个专栏,专栏目前已经创作了 108 篇文章,只需要 29.9 元。点击【阅读原文】,就可以购买继续阅读 ~
WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。